#pragma once

#include <prsht.h>

class CSettingsPage
{
public:
    virtual ~CSettingsPage(void) = 0 {};

    virtual LanguageStringID PageTitle(void) = 0;
    virtual void HidePage(void) = 0;
    virtual void ShowPage(void) = 0;
    virtual void ApplySettings(bool UpdateScreen) = 0;
    virtual bool EnableReset(void) = 0;
    virtual void ResetPage(void) = 0;
};

template <class T>
class CSettingsPageImpl :
    public CDialogImpl<T>
{
protected:
    typedef std::map<SettingID, CModifiedEditBox *> TextBoxList;
    typedef std::map<SettingID, CModifiedButton *> ButtonList;
    typedef std::map<SettingID, CModifiedComboBox *> ComboBoxList;
    typedef std::map<SettingID, CModifiedComboBoxTxt *> ComboBoxTxtList;

    virtual ~CSettingsPageImpl()
    {
        for (TextBoxList::iterator eb_iter = m_TxtBoxList.begin(); eb_iter != m_TxtBoxList.end(); eb_iter++)
        {
            delete eb_iter->second;
        }
        for (ButtonList::iterator iter = m_ButtonList.begin(); iter != m_ButtonList.end(); iter++)
        {
            delete iter->second;
        }
        for (ComboBoxTxtList::iterator cbtxt_iter = m_ComboBoxTxtList.begin(); cbtxt_iter != m_ComboBoxTxtList.end(); cbtxt_iter++)
        {
            delete cbtxt_iter->second;
        }
        for (ComboBoxList::iterator cb_iter = m_ComboBoxList.begin(); cb_iter != m_ComboBoxList.end(); cb_iter++)
        {
            delete cb_iter->second;
        }
    }
    bool Create(HWND hParent, const RECT & rcDispay)
    {
        BOOL result = m_thunk.Init(nullptr, nullptr);
        if (result == FALSE)
        {
            SetLastError(ERROR_OUTOFMEMORY);
            return false;
        }

        _AtlWinModule.AddCreateWndData(&m_thunk.cd, this);
#ifdef _DEBUG
        m_bModal = false;
#endif //_DEBUG
        m_hWnd = ::CreateDialogParam(_AtlBaseModule.GetResourceInstance(), MAKEINTRESOURCE(static_cast<T *>(this)->IDD), hParent, T::StartDialogProc, NULL);
        if (m_hWnd == nullptr)
        {
            return false;
        }
        SetWindowPos(HWND_TOP, &rcDispay, SWP_HIDEWINDOW);
        m_UpdatingTxt = false;
        return true;
    }

    void CheckBoxChanged(UINT /*Code*/, int id, HWND /*ctl*/)
    {
        for (ButtonList::iterator iter = m_ButtonList.begin(); iter != m_ButtonList.end(); iter++)
        {
            CModifiedButton * Button = iter->second;
            if ((INT_PTR)Button->GetMenu() != id)
            {
                continue;
            }
            Button->SetChanged(true);
            SendMessage(GetParent(), PSM_CHANGED, (WPARAM)m_hWnd, 0);
            break;
        }
    }

    void UpdateModEditBox(CModifiedEditBox & EditBox, SettingID Type)
    {
        if (EditBox.IsChanged())
        {
            stdstr Value = GetCWindowText(EditBox);
            if (EditBox.IsbString())
            {
                g_Settings->SaveString(Type, Value);
            }
            else
            {
                DWORD dwValue = atoi(Value.c_str());
                g_Settings->SaveDword(Type, dwValue);
            }
        }
        if (EditBox.IsReset())
        {
            g_Settings->DeleteSetting(Type);
            EditBox.SetReset(false);
        }
    }

    void UpdateModCheckBox(CModifiedButton & CheckBox, SettingID Type)
    {
        if (CheckBox.IsChanged())
        {
            bool bValue = CheckBox.GetCheck() == BST_CHECKED;
            if (bValue != g_Settings->LoadBool(Type))
            {
                g_Settings->SaveBool(Type, bValue);
            }
        }
        if (CheckBox.IsReset())
        {
            g_Settings->DeleteSetting(Type);
            CheckBox.SetReset(false);
        }
    }

    bool ResetCheckBox(CModifiedButton & CheckBox, SettingID Type)
    {
        if (!CheckBox.IsChanged())
        {
            return false;
        }

        bool Value = g_Settings->LoadDefaultBool(Type);
        CheckBox.SetReset(true);
        CheckBox.SetCheck(Value ? BST_CHECKED : BST_UNCHECKED);
        return true;
    }

    bool ResetEditBox(CModifiedEditBox & EditBox, SettingID Type)
    {
        if (!EditBox.IsChanged())
        {
            return false;
        }

        if (EditBox.IsbString())
        {
            stdstr Value = g_Settings->LoadDefaultString(Type);
            EditBox.SetWindowText(Value.ToUTF16().c_str());
            EditBox.SetReset(true);
        }
        else
        {
            DWORD Value = g_Settings->LoadDefaultDword(Type);
            EditBox.SetWindowText(stdstr_f("%d", Value).ToUTF16().c_str());
            EditBox.SetReset(true);
        }
        return true;
    }

    CModifiedEditBox * AddModTextBox(HWND hWnd, SettingID Type, bool bString)
    {
        TextBoxList::iterator item = m_TxtBoxList.find(Type);
        if (item == m_TxtBoxList.end())
        {
            CModifiedEditBox * EditBox = new CModifiedEditBox(bString);
            if (EditBox == nullptr)
            {
                return nullptr;
            }
            EditBox->Attach(hWnd);

            m_TxtBoxList.insert(TextBoxList::value_type(Type, EditBox));
            return EditBox;
        }
        return nullptr;
    }

    void AddModCheckBox(HWND hWnd, SettingID Type)
    {
        ButtonList::iterator item = m_ButtonList.find(Type);
        if (item == m_ButtonList.end())
        {
            CModifiedButton * Button = new CModifiedButton;
            if (Button == nullptr)
            {
                return;
            }
            Button->Attach(hWnd);

            m_ButtonList.insert(ButtonList::value_type(Type, Button));
        }
    }

    CModifiedComboBox * AddModComboBox(HWND hWnd, SettingID Type)
    {
        ComboBoxList::iterator item = m_ComboBoxList.find(Type);
        if (item != m_ComboBoxList.end())
        {
            return item->second;
        }

        CModifiedComboBox * ComboBox = new CModifiedComboBox(g_Settings->LoadDefaultDword(Type), nullptr, false);
        if (ComboBox == nullptr)
        {
            return nullptr;
        }
        ComboBox->Attach(hWnd);
        m_ComboBoxList.insert(ComboBoxList::value_type(Type, ComboBox));
        return ComboBox;
    }

    CModifiedComboBoxTxt * AddModComboBoxTxt(HWND hWnd, SettingID Type)
    {
        ComboBoxTxtList::iterator item = m_ComboBoxTxtList.find(Type);
        if (item != m_ComboBoxTxtList.end())
        {
            return item->second;
        }

        CModifiedComboBoxTxt * ComboBox = new CModifiedComboBoxTxt(g_Settings->LoadDefaultString(Type));
        if (ComboBox == nullptr)
        {
            return nullptr;
        }
        ComboBox->Attach(hWnd);
        m_ComboBoxTxtList.insert(ComboBoxTxtList::value_type(Type, ComboBox));
        return ComboBox;
    }

    void UpdateCheckBoxes(void)
    {
        for (ButtonList::iterator iter = m_ButtonList.begin(); iter != m_ButtonList.end(); iter++)
        {
            CModifiedButton * Button = iter->second;
            bool SettingSelected;

            Button->SetChanged(g_Settings->LoadBool(iter->first, SettingSelected));
            Button->SetCheck(SettingSelected ? BST_CHECKED : BST_UNCHECKED);
        }
    }

    void UpdateComboBoxes(void)
    {
        for (ComboBoxTxtList::iterator cbtxt_iter = m_ComboBoxTxtList.begin(); cbtxt_iter != m_ComboBoxTxtList.end(); cbtxt_iter++)
        {
            CModifiedComboBoxTxt * ComboBox = cbtxt_iter->second;
            stdstr SelectedValue;

            ComboBox->SetChanged(g_Settings->LoadStringVal(cbtxt_iter->first, SelectedValue));
            ComboBox->SetDefault(SelectedValue);
        }

        for (ComboBoxList::iterator cb_iter = m_ComboBoxList.begin(); cb_iter != m_ComboBoxList.end(); cb_iter++)
        {
            CModifiedComboBox * ComboBox = cb_iter->second;
            uint32_t SelectedValue;

            ComboBox->SetChanged(g_Settings->LoadDword(cb_iter->first, SelectedValue));
            ComboBox->SetDefault(SelectedValue);
        }
    }

    void UpdateTextBoxes(void)
    {
        for (TextBoxList::iterator iter = m_TxtBoxList.begin(); iter != m_TxtBoxList.end(); iter++)
        {
            CModifiedEditBox * TextBox = iter->second;

            m_UpdatingTxt = true;
            if (TextBox->IsbString())
            {
                stdstr SelectedValue;
                TextBox->SetChanged(g_Settings->LoadStringVal(iter->first, SelectedValue));
                TextBox->SetWindowText(SelectedValue.ToUTF16().c_str());
            }
            else
            {
                uint32_t SelectedValue;
                TextBox->SetChanged(g_Settings->LoadDword(iter->first, SelectedValue));
                TextBox->SetWindowText(stdstr_f("%d", SelectedValue).ToUTF16().c_str());
            }
            m_UpdatingTxt = false;
        }
    }

    virtual void UpdatePageSettings(void)
    {
        UpdateCheckBoxes();
        UpdateComboBoxes();
        UpdateTextBoxes();
    }

    void ComboBoxChanged(UINT /*Code*/, int id, HWND /*ctl*/)
    {
        for (ComboBoxTxtList::iterator cbtxt_iter = m_ComboBoxTxtList.begin(); cbtxt_iter != m_ComboBoxTxtList.end(); cbtxt_iter++)
        {
            CModifiedComboBoxTxt * ComboBox = cbtxt_iter->second;
            if ((INT_PTR)ComboBox->GetMenu() != id)
            {
                continue;
            }
            ComboBox->SetChanged(true);
            SendMessage(GetParent(), PSM_CHANGED, (WPARAM)m_hWnd, 0);
            break;
        }
        for (ComboBoxList::iterator cb_iter = m_ComboBoxList.begin(); cb_iter != m_ComboBoxList.end(); cb_iter++)
        {
            CModifiedComboBox * ComboBox = cb_iter->second;
            if ((INT_PTR)ComboBox->GetMenu() != id)
            {
                continue;
            }
            ComboBox->SetChanged(true);
            SendMessage(GetParent(), PSM_CHANGED, (WPARAM)m_hWnd, 0);
            break;
        }
    }

    void EditBoxChanged(UINT /*Code*/, int id, HWND /*ctl*/)
    {
        if (m_UpdatingTxt)
        {
            return;
        }

        for (TextBoxList::iterator eb_iter = m_TxtBoxList.begin(); eb_iter != m_TxtBoxList.end(); eb_iter++)
        {
            CModifiedEditBox * EditBox = eb_iter->second;
            if ((INT_PTR)EditBox->GetMenu() != id)
            {
                continue;
            }
            EditBox->SetChanged(true);
            SendMessage(GetParent(), PSM_CHANGED, (WPARAM)m_hWnd, 0);
            break;
        }
    }

    virtual bool ResetComboBox(CModifiedComboBox & ComboBox, SettingID Type)
    {
        if (!ComboBox.IsChanged())
        {
            return false;
        }

        ComboBox.SetReset(true);
        DWORD Value = g_Settings->LoadDefaultDword(Type);
        for (int i = 0, n = ComboBox.GetCount(); i < n; i++)
        {
            if (*((WPARAM *)ComboBox.GetItemData(i)) != Value)
            {
                continue;
            }
            ComboBox.SetCurSel(i);
            return true;
        }
        return false;
    }

    virtual bool ResetComboBoxTxt(CModifiedComboBoxTxt & ComboBox, SettingID Type)
    {
        if (!ComboBox.IsChanged())
        {
            return false;
        }

        ComboBox.SetReset(true);
        stdstr Value = g_Settings->LoadDefaultString(Type);
        for (int i = 0, n = ComboBox.GetCount(); i < n; i++)
        {
            if (*((stdstr *)ComboBox.GetItemData(i)) != Value)
            {
                continue;
            }
            ComboBox.SetCurSel(i);
            return true;
        }
        return false;
    }

    virtual void UpdateModComboBox(CModifiedComboBox & ComboBox, SettingID Type)
    {
        if (ComboBox.IsChanged())
        {
            int index = ComboBox.GetCurSel();
            if (index == CB_ERR)
            {
                return;
            }
            g_Settings->SaveDword(Type, *(DWORD *)ComboBox.GetItemData(index));
        }
        if (ComboBox.IsReset())
        {
            g_Settings->DeleteSetting(Type);
            ComboBox.SetReset(false);
        }
    }

    virtual void UpdateModComboBoxTxt(CModifiedComboBoxTxt & ComboBox, SettingID Type)
    {
        if (ComboBox.IsChanged())
        {
            int index = ComboBox.GetCurSel();
            if (index == CB_ERR)
            {
                return;
            }
            g_Settings->SaveString(Type, ((stdstr *)ComboBox.GetItemData(index))->c_str());
        }
        if (ComboBox.IsReset())
        {
            g_Settings->DeleteSetting(Type);
            ComboBox.SetReset(false);
        }
    }

public:
    virtual void ApplyEditBoxes(void)
    {
        for (TextBoxList::iterator eb_iter = m_TxtBoxList.begin(); eb_iter != m_TxtBoxList.end(); eb_iter++)
        {
            CModifiedEditBox * EditBox = eb_iter->second;
            UpdateModEditBox(*EditBox, eb_iter->first);
        }
    }
    virtual void ApplyCheckBoxes(void)
    {
        for (ButtonList::iterator iter = m_ButtonList.begin(); iter != m_ButtonList.end(); iter++)
        {
            CModifiedButton * Button = iter->second;
            UpdateModCheckBox(*Button, iter->first);
        }
    }
    virtual void ApplyComboBoxes(void)
    {
        for (ComboBoxTxtList::iterator cbtxt_iter = m_ComboBoxTxtList.begin(); cbtxt_iter != m_ComboBoxTxtList.end(); cbtxt_iter++)
        {
            CModifiedComboBoxTxt * ComboBox = cbtxt_iter->second;
            UpdateModComboBoxTxt(*ComboBox, cbtxt_iter->first);
        }
        for (ComboBoxList::iterator cb_iter = m_ComboBoxList.begin(); cb_iter != m_ComboBoxList.end(); cb_iter++)
        {
            CModifiedComboBox * ComboBox = cb_iter->second;
            UpdateModComboBox(*ComboBox, cb_iter->first);
        }
    }
    void ApplySettings(bool UpdateScreen)
    {
        ApplyEditBoxes();
        ApplyCheckBoxes();
        ApplyComboBoxes();

        if (UpdateScreen)
        {
            UpdatePageSettings();
        }
    }

    bool EnableReset(void)
    {
        for (TextBoxList::iterator eb_iter = m_TxtBoxList.begin(); eb_iter != m_TxtBoxList.end(); eb_iter++)
        {
            CModifiedEditBox * EditBox = eb_iter->second;
            if (EditBox->IsChanged())
            {
                return true;
            }
        }
        for (ButtonList::iterator iter = m_ButtonList.begin(); iter != m_ButtonList.end(); iter++)
        {
            CModifiedButton * Button = iter->second;
            if (Button->IsChanged())
            {
                return true;
            }
        }
        for (ComboBoxTxtList::iterator cbtxt_iter = m_ComboBoxTxtList.begin(); cbtxt_iter != m_ComboBoxTxtList.end(); cbtxt_iter++)
        {
            CModifiedComboBoxTxt * ComboBox = cbtxt_iter->second;
            if (ComboBox->IsChanged())
            {
                return true;
            }
        }
        for (ComboBoxList::iterator cb_iter = m_ComboBoxList.begin(); cb_iter != m_ComboBoxList.end(); cb_iter++)
        {
            CModifiedComboBox * ComboBox = cb_iter->second;
            if (ComboBox->IsChanged())
            {
                return true;
            }
        }
        return false;
    }

    void ResetPage(void)
    {
        bool Changed = false;
        for (TextBoxList::iterator eb_iter = m_TxtBoxList.begin(); eb_iter != m_TxtBoxList.end(); eb_iter++)
        {
            CModifiedEditBox * EditBox = eb_iter->second;
            if (ResetEditBox(*EditBox, eb_iter->first))
            {
                Changed = true;
            }
        }
        for (ButtonList::iterator iter = m_ButtonList.begin(); iter != m_ButtonList.end(); iter++)
        {
            CModifiedButton * Button = iter->second;
            if (ResetCheckBox(*Button, iter->first))
            {
                Changed = true;
            }
        }
        for (ComboBoxTxtList::iterator cbtxt_iter = m_ComboBoxTxtList.begin(); cbtxt_iter != m_ComboBoxTxtList.end(); cbtxt_iter++)
        {
            CModifiedComboBoxTxt * ComboBox = cbtxt_iter->second;
            if (ResetComboBoxTxt(*ComboBox, cbtxt_iter->first))
            {
                Changed = true;
            }
        }
        for (ComboBoxList::iterator cb_iter = m_ComboBoxList.begin(); cb_iter != m_ComboBoxList.end(); cb_iter++)
        {
            CModifiedComboBox * ComboBox = cb_iter->second;
            if (ResetComboBox(*ComboBox, cb_iter->first))
            {
                Changed = true;
            }
        }
        if (Changed)
        {
            SendMessage(GetParent(), PSM_CHANGED, (WPARAM)m_hWnd, 0);
        }
    }

protected:
    TextBoxList m_TxtBoxList;
    ButtonList m_ButtonList;
    ComboBoxList m_ComboBoxList;
    ComboBoxTxtList m_ComboBoxTxtList;
    bool m_UpdatingTxt;
};

typedef std::vector<CSettingsPage *> SETTING_PAGES;

class CConfigSettingSection
{
    SETTING_PAGES m_Pages;
    std::wstring m_PageTitle;

public:
    CConfigSettingSection(LPCWSTR PageTitle);
    ~CConfigSettingSection();

    LPCWSTR GetPageTitle(void) const
    {
        return m_PageTitle.c_str();
    }
    void AddPage(CSettingsPage * Page);
    size_t GetPageCount(void) const
    {
        return m_Pages.size();
    }
    CSettingsPage * GetPage(int PageNo);
};

#include "SettingsPage-AdvancedOptions.h"
#include "SettingsPage-Defaults.h"
#include "SettingsPage-Directories.h"
#include "SettingsPage-DiskDrive.h"
#include "SettingsPage-Game-DiskDrive.h"
#include "SettingsPage-Game-General.h"
#include "SettingsPage-Game-Plugin.h"
#include "SettingsPage-Game-Recompiler.h"
#include "SettingsPage-Game-Status.h"
#include "SettingsPage-GameBrowser.h"
#include "SettingsPage-KeyboardShortcuts.h"
#include "SettingsPage-Options.h"
#include "SettingsPage-Plugin.h"
